package com.farmer.mybatis.plugins;


import org.mybatis.generator.api.GeneratedJavaFile;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.DefaultJavaFormatter;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.api.dom.xml.Element;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.config.MergeConstants;
import org.mybatis.generator.internal.util.StringUtility;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author tech-farmer
 * @ClassName: BaseClassPlugin
 * @Description: (这里用一句话描述这个类的作用)
 * @date 2021/11/5 13:23
 */
public class BaseClassPlugin extends PluginAdapter {

    private String baseEntityName;
    private String baseMapperName;
    private TopLevelClass subEntityClass;
    private Interface subMapperClass;

    private boolean useBaseEntity;
    private boolean useBaseMapper;

    @Override
    public boolean validate(List<String> list) {
        useBaseEntity = StringUtility.isTrue(this.getProperties().getProperty("useBaseEntity","true"));
        useBaseMapper = StringUtility.isTrue(this.getProperties().getProperty("useBaseMapper","true"));
        return true;
    }

    public BaseClassPlugin() {
    }

    @Override
    public void initialized(IntrospectedTable introspectedTable) {
        super.initialized(introspectedTable);
        if(useBaseEntity){
            String baseRecordType = introspectedTable.getBaseRecordType();
            String[] split = baseRecordType.split("\\.");
            StringBuilder sb = new StringBuilder();
            for (int i = 0,len = split.length; i < len; i++) {
                if(i == len - 1){
                    sb.append("Base");
                }
                sb.append(split[i]);
                if(i != len - 1){
                    sb.append(".");
                }
            }
            baseEntityName = sb.toString();
            introspectedTable.setBaseRecordType(baseEntityName);
            subEntityClass = new TopLevelClass(baseRecordType);
            subEntityClass.setVisibility(JavaVisibility.PUBLIC);
            subEntityClass.setSuperClass(baseEntityName);
        }
        if(useBaseMapper){
            String myBatis3JavaMapperType = introspectedTable.getMyBatis3JavaMapperType();
            String[] split = myBatis3JavaMapperType.split("\\.");
            StringBuilder sb = new StringBuilder();
            for (int i = 0,len = split.length; i < len; i++) {
                if(i == len - 1){
                    sb.append("Base");
                }
                sb.append(split[i]);
                if(i != len - 1){
                    sb.append(".");
                }
            }
            baseMapperName = sb.toString();
            introspectedTable.setMyBatis3JavaMapperType(baseMapperName);
            subMapperClass = new Interface(myBatis3JavaMapperType);
            subMapperClass.setVisibility(JavaVisibility.PUBLIC);
        }
        try {
            Constructor<?>[] constructors = MergeConstants.class.getDeclaredConstructors();
            constructors[0].setAccessible(true);
            Object newInstance = constructors[0].newInstance();
            java.lang.reflect.Field field = MergeConstants.class.getField("OLD_XML_ELEMENT_PREFIXES");
            field.setAccessible(true);
            java.lang.reflect.Field modifiers = field.getClass().getDeclaredField("modifiers");
            modifiers.setAccessible(true);
            modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            field.set(newInstance,new String[]{"updateByPrimaryKey", "updateByPrimaryKeySelective","updateByExample","updateByExampleSelective","countByExample","insertSelective","insert","deleteByExample","deleteByPrimaryKey","selectByPrimaryKey","Update_By_Example_Where_Clause","Example_Where_Clause","BaseResultMap","Base_Column_List","selectByExample","UpdateByExampleWithBLOBs","SelectByExampleWithBLOBs","ResultMapWithBLOBs","Blob_Column_List"});
            modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
        XmlElement rootElement = document.getRootElement();
        if(useBaseMapper){
            List<Attribute> attributes = rootElement.getAttributes();
            Iterator<Attribute> iterator = attributes.iterator();
            String attrName = null;
            while (iterator.hasNext()){
                attrName = null;
                Attribute next = iterator.next();
                if(next.getValue().equals(baseMapperName)){
                    attrName = next.getName();
                    iterator.remove();
                }
            }
            if(attrName!= null){
                attributes.add(new Attribute(attrName,subMapperClass.getType().getFullyQualifiedName()));
            }
        }

        if(useBaseEntity){
            List<Element> elements = rootElement.getElements();
            for (Element element : elements) {
                XmlElement  xl = (XmlElement) element;
                List<Attribute> attributes = xl.getAttributes();
                Iterator<Attribute> iterator = attributes.iterator();
                String attrName = null;
                while (iterator.hasNext()){
                    attrName = null;
                    Attribute next = iterator.next();
                    if(next.getValue().equals(baseEntityName)){
                        attrName = next.getName();
                        iterator.remove();
                    }
                }
                if(attrName!= null){
                    attributes.add(new Attribute(attrName,subEntityClass.getType().getFullyQualifiedName()));
                }
            }
        }
        return true;
    }

    @Override
    public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {
        List<GeneratedJavaFile> awser = new ArrayList<>(2);
        String targetProject = introspectedTable.getContext().getJavaModelGeneratorConfiguration().getTargetProject();
        File subEntityFile = new File(targetProject + "/" + subEntityClass.getType().getFullyQualifiedName().replace(".", "/") + ".java");
        if(useBaseEntity && !subEntityFile.exists()){
            GeneratedJavaFile javaEntityFile = new GeneratedJavaFile(subEntityClass,targetProject,new DefaultJavaFormatter());
            awser.add(javaEntityFile);
        }
        File subMapperFile = new File(targetProject + "/" + subMapperClass.getType().getFullyQualifiedName().replace(".", "/") + ".java");
        if(useBaseMapper && !subMapperFile.exists()){
            GeneratedJavaFile javaMapperFile = new GeneratedJavaFile(subMapperClass,targetProject,new DefaultJavaFormatter());
            awser.add(javaMapperFile);
        }
        return awser;
    }

    public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        if(useBaseEntity){
            interfaze.addImportedType(subEntityClass.getType());
        }
        if(useBaseMapper){
            subMapperClass.addSuperInterface(interfaze.getType());
        }
        if(useBaseEntity){
            for (Method method : interfaze.getMethods()) {
                List<Parameter> paramList = new ArrayList<>();
                List<Parameter> parameters = method.getParameters();
                Iterator<Parameter> iterator = parameters.iterator();
                while (iterator.hasNext()){
                    Parameter next = iterator.next();
                    if (next.getType().getFullyQualifiedName().equals(baseEntityName)) {
                        paramList.add(new Parameter(subEntityClass.getType(),next.getName(),next.getAnnotations().stream().collect(Collectors.joining(" ")),next.isVarargs()));
                    }else{
                        paramList.add(new Parameter(next.getType(),next.getName(),next.getAnnotations().stream().collect(Collectors.joining(" ")),next.isVarargs()));
                    }
                    iterator.remove();
                }
                parameters.addAll(paramList);


                FullyQualifiedJavaType returnType = method.getReturnType();
                if(returnType.getFullyQualifiedName().equals(baseEntityName)){
                    method.setReturnType(subEntityClass.getType());
                }

                List<FullyQualifiedJavaType> typeArguments = returnType.getTypeArguments();
                List<FullyQualifiedJavaType> typwArgList = new ArrayList<>();
                Iterator<FullyQualifiedJavaType> typeIterator = typeArguments.iterator();
                while (typeIterator.hasNext()){
                    FullyQualifiedJavaType next = typeIterator.next();
                    if (next.getFullyQualifiedName().equals(baseEntityName)) {
                        typwArgList.add(subEntityClass.getType());
                    }else{
                        typwArgList.add(next);
                    }
                    typeIterator.remove();
                }
                typeArguments.addAll(typwArgList);
            }

            interfaze.getImportedTypes().removeIf(next -> next.getFullyQualifiedName().equals(baseEntityName));
        }
        return true;
    }
}
